﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using pwestcott.Util;

namespace pwestcott.Utils.PerformanceTests.Essence
{
    class EqualsTests : ITest
    {
        interface IMyData
        {
            int MyInt { get; set; }
            string MyString { get; set; }
            DateTime MyDateTime { get; set; }
        }

        [EssenceClass(UseIn = EssenceFunctions.All)]
        class TestEssenceStatic : IEquatable<TestEssenceStatic>
        {
            static readonly Func<TestEssenceStatic, TestEssenceStatic, bool> equals = Essence<TestEssenceStatic>.EqualsStatic;

            [Essence(Order = 0)] public int MyInt { get; set; }
            [Essence(Order = 1)] public string MyString { get; set; }
            [Essence(Order = 2)] public DateTime MyDateTime { get; set; }

            public bool Equals(TestEssenceStatic other)
            {
                return equals(this, other);
            }

            public override bool Equals(object obj)
            {
                throw new InvalidOperationException("Wrong Equals");
            }

            public override int GetHashCode()
            {
                throw new InvalidOperationException("No use for GetHashCode");
            }
        }

        class TestEssenceMember : IEquatable<TestEssenceMember>
        {
            private readonly Func<TestEssenceMember, bool> equals;

            [Essence(Order = 0)] public int MyInt { get; set; }
            [Essence(Order = 1)] public string MyString { get; set; }
            [Essence(Order = 2)] public DateTime MyDateTime { get; set; }

            public TestEssenceMember()
            {
                equals = Essence<TestEssenceMember>.EqualsInstance(this);
            }

            public bool Equals(TestEssenceMember other)
            {
                return equals(other);
            }

            public override bool Equals(object obj)
            {
                throw new InvalidOperationException("Wrong Equals");
            }

            public override int GetHashCode()
            {
                throw new InvalidOperationException("No use for GetHashCode");
            }
        }

        class TestManual : IEquatable<TestManual>
        {
            public int MyInt { get; set; }
            public string MyString { get; set; }
            public DateTime MyDateTime { get; set; }

            public bool Equals(TestManual other)
            {
                if (other == null)
                    return false;
                if (ReferenceEquals(this, other))
                    return true;
                if (typeof(TestManual) != other.GetType())
                    return false;

                if (!MyInt.Equals(other.MyInt))
                    return false;
                if (MyString == null)
                {
                    if (other.MyString != null)
                        return false;
                }
                else
                {
                    if (!MyString.Equals(other.MyString))
                        return false;
                }
                if (!MyDateTime.Equals(other.MyDateTime))
                    return false;

                return true;
            }

            public override bool Equals(object obj)
            {
                throw new InvalidOperationException("Wrong Equals");
            }

            public override int GetHashCode()
            {
                throw new InvalidOperationException("No use for GetHashCode");
            }
        }


        class TestManualOptimized : IEquatable<TestManualOptimized>
        {
            public int MyInt { get; set; }
            public string MyString { get; set; }
            public DateTime MyDateTime { get; set; }

            public bool Equals(TestManualOptimized other)
            {
                if (other == null)
                    return false;
                if (ReferenceEquals(this, other))
                    return true;
                if (typeof(TestManualOptimized) != other.GetType())
                    return false;

                return (MyInt == other.MyInt)
                    && (MyString == other.MyString)
                    && (MyDateTime == other.MyDateTime);
            }

            public override bool Equals(object obj)
            {
                throw new InvalidOperationException("Wrong Equals");
            }

            public override int GetHashCode()
            {
                throw new InvalidOperationException("No use for GetHashCode");
            }
        }


        public int TestObj<T>(string name, int iterations, T x1, T x2, T x3)
            where T : IEquatable<T>
        {
            int total = 0;

            DateTime start = DateTime.Now;
            for (int i = 0; i < iterations; ++i)
            {
                unchecked
                {
                    if (x1.Equals(x1))
                        ++total;
                    if (x1.Equals(x2))
                        ++total;
                    if (x2.Equals(x3))
                        ++total;
                }
            }
            DateTime end = DateTime.Now;

            Console.WriteLine("{0} {1}", name, (end-start).TotalMilliseconds);

            return total;
        }

        public void Run()
        {
            const int iterations = 10000000;

            var now = DateTime.Now;

            var staticTest = new TestEssenceStatic();
            var memberTest = new TestEssenceMember();
            var manualTest = new TestManual();
            var manualTestOptimized = new TestManualOptimized();

            staticTest.MyDateTime = memberTest.MyDateTime = manualTest.MyDateTime = manualTestOptimized.MyDateTime = now;
            staticTest.MyInt      = memberTest.MyInt      = manualTest.MyInt      = manualTestOptimized.MyInt      = 42;
            staticTest.MyString   = memberTest.MyString   = manualTest.MyString   = manualTestOptimized.MyString   = "The Quick Brown Fox Jumps Over The Lazy Dog";

            var staticTestSame = new TestEssenceStatic();
            var memberTestSame = new TestEssenceMember();
            var manualTestSame = new TestManual();
            var manualTestSameOptimized = new TestManualOptimized();

            staticTestSame.MyDateTime = memberTestSame.MyDateTime = manualTestSame.MyDateTime = manualTestSameOptimized.MyDateTime = now;
            staticTestSame.MyInt      = memberTestSame.MyInt      = manualTestSame.MyInt      = manualTestSameOptimized.MyInt      = 42;
            staticTestSame.MyString   = memberTestSame.MyString   = manualTestSame.MyString   = manualTestSameOptimized.MyString   = "The Quick Brown Fox Jumps Over The Lazy Dog";

            var staticTestDifferent = new TestEssenceStatic();
            var memberTestDifferent = new TestEssenceMember();
            var manualTestDifferent = new TestManual();
            var manualTestDifferentOptimized = new TestManualOptimized();

            staticTestDifferent.MyDateTime = memberTestDifferent.MyDateTime = manualTestDifferent.MyDateTime = manualTestDifferentOptimized.MyDateTime = new DateTime(2000, 1, 1);
            staticTestDifferent.MyInt      = memberTestDifferent.MyInt      = manualTestDifferent.MyInt      = manualTestDifferentOptimized.MyInt      = 16;
            staticTestDifferent.MyString   = memberTestDifferent.MyString   = manualTestDifferent.MyString   = manualTestDifferentOptimized.MyString   = null;

            var sanity1 = TestObj("static",           iterations, staticTest,          staticTestSame,          staticTestDifferent);
            var sanity2 = TestObj("member",           iterations, memberTest,          memberTestSame,          memberTestDifferent);
            var sanity3 = TestObj("manual",           iterations, manualTest,          manualTestSame,          manualTestDifferent);
            var sanity4 = TestObj("manual optimized", iterations, manualTestOptimized, manualTestSameOptimized, manualTestDifferentOptimized);

            if (sanity1 == sanity2 && sanity1 == sanity3 && sanity1 == sanity4)
                Console.WriteLine("Sanity check passed ({0})", sanity1);
            else
                Console.WriteLine("Sanity check failed ({0}, {1}, {2}, {3})", sanity1, sanity2, sanity3, sanity4);
        }
    }
}
